// <copyright file="WsTrustResponder.cs" company="SharpSTS">
// Copyright (c) 2007, 2008 All Right Reserved, http://sharpsts.com/
//
// This source is subject to the Microsoft Permissive License.
// Please see the License.txt file for more information.
// All other rights reserved.
//
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// </copyright>
// <author>Barry Dorrans</author>
// <email>barryd@idunno.org</email>
// <date>2008-06-16</date>
// <summary>An abstract class representing a WSTrustResponder service.</summary>

namespace SharpSTS
{
    using System;
    using System.Collections.Generic;
    using System.Security;
    using System.ServiceModel;
    using System.ServiceModel.Channels;

    using Configuration;
    using Properties;
    using Providers;    
    using RST = RequestSecurityToken;
    using RSTR = RequestSecurityTokenResponse;

    /// <summary>
    /// An abstract class representing a WSTrustResponder service.
    /// </summary>
    [ServiceBehavior(Namespace = Constants.WSTrust.Namespace.Uri)]
    internal abstract class WsTrustResponder : IWSTrustContract
    {
        /// <summary>
        /// Provides a syncronisation object for threadsafe locking.
        /// </summary>
        private static readonly object synclock = new object();

        /// <summary>
        /// A list of supported claims, sourced from the configuration file.
        /// </summary>
        private static List<string> supportedClaimUris;

        /// <summary>
        /// The WS-Trust Cancel binding.
        /// </summary>
        /// <param name="request">A RequestSecurityToken (or RequestSecurityTokenResponse) message, 
        /// with WS-Addressing Action http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Cancel</param>
        /// <returns>A RequestSecurityTokenResponse message.</returns>
        public Message Cancel(Message request)
        {
            throw new FaultException(Resources.ErrorActionNotImplemented);
        }

        /// <summary>
        /// The WS-Trust Issue binding.
        /// </summary>
        /// <param name="request">A RequestSecurityToken (or RequestSecurityTokenResponse) message, 
        /// with WS-Addressing Action http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue
        /// </param>
        /// <returns>A RequestSecurityTokenResponse message.</returns>
        public Message Issue(Message request)
        {
            try
            {
                if (request == null)
                {
                    throw new ArgumentNullException("request");
                }

                // Parse the incoming request to get an RST
                RST rst = new RST(request.GetReaderAtBodyContents());

                // Validate the RST
                if (!this.AuthoriseCardUse(rst.CardId, rst.CardVersion))
                {
                    throw new UnknownCardReferenceException(rst.CardId, rst.CardVersion);
                }

                if (AuthorisationPolicyManager.IsCardRefreshRequired(rst.CardId, rst.CardVersion))
                {
                    throw new InformationCardRefreshRequiredException(rst.CardId, rst.CardVersion);
                }

                // Check the claims requested are ones we support
                if (null == supportedClaimUris)
                {
                    lock (synclock)
                    {
                        if (null == supportedClaimUris)
                        {
                            supportedClaimUris = new List<string>();
                            foreach (SupportedClaim supportedClaim in STSSettings.Settings.SupportedClaims)
                            {
                                supportedClaimUris.Add(supportedClaim.Uri);
                            }
                        }
                    }
                }

                List<string> unsupportedClaims = new List<string>();
                foreach (string requestedClaim in rst.RequestedClaims)
                {
                    if (!supportedClaimUris.Contains(requestedClaim))
                    {
                        unsupportedClaims.Add(requestedClaim);
                    }
                }

                if (unsupportedClaims.Count > 0)
                {
                    throw new FailedRequiredClaimsException(unsupportedClaims);
                }

                if (STSSettings.Settings.RequireRelyingPartyAddress &&
                    (rst.RelyingPartyEndpoint.IsNone || rst.RelyingPartyEndpoint.Uri == null))
                {
                    throw new MissingAppliesToException();
                }

                if (!EndpointValidationProviderManager.IsValid(rst.RelyingPartyEndpoint, rst.RequestedClaims))
                {
                    throw new SecurityException(Resources.ErrorEndpointValidationFailed);
                }

                if (STSSettings.Settings.RequireSecureRelyingParty &&
                    string.Compare(rst.RelyingPartyEndpoint.Uri.Scheme, "https", StringComparison.OrdinalIgnoreCase) != 0)
                {
                    throw new SecurityException(Resources.ErrorHTTPRequirementsFailed);
                }

                // Process the request and generate an RSTR
                RSTR rstr = new RSTR(rst, this.GetUserIdentifierFromCardId(rst.CardId));

                // Generate a response message
                Message response = Message.CreateMessage(MessageVersion.Default, Constants.WSTrust.Actions.IssueResponse, rstr);

                // Set the RelatesTo
                if (request.Headers.MessageId != null)
                {
                    response.Headers.RelatesTo = request.Headers.MessageId;
                }
                else
                {
                    throw new NotSupportedException(Resources.ErrorMissingMessageID);
                }

                // Send back to the caller
                return response;
            }
            catch (WSTrustFaultException)
            {
                throw;
            }
            catch (Exception e)
            {
                throw WSTrustFaultException.FromException(e);
            }
        }

        /// <summary>
        /// The WS-Trust Renew binding.
        /// </summary>
        /// <param name="request">A RequestSecurityToken (or RequestSecurityTokenResponse) message, 
        /// with WS-Addressing Action http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Renew
        /// </param>
        /// <returns>A RequestSecurityTokenResponse message.</returns>
        public Message Renew(Message request)
        {
            throw new FaultException<string>(Resources.ErrorActionNotSupported);
        }

        /// <summary>
        /// The WS-Trust Validate binding.
        /// </summary>
        /// <param name="request">A RequestSecurityToken (or RequestSecurityTokenResponse) message, 
        /// with WS-Addressing Action http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Validate
        /// </param>
        /// <returns>A RequestSecurityTokenResponse message.</returns>
        public Message Validate(Message request)
        {
            throw new FaultException<string>(Resources.ErrorActionNotSupported);
        }

        /// <summary>
        /// Validate the specified card identifier and version against the endpoint specific authentication information.
        /// </summary>
        /// <param name="cardId">The card identitifer for the current request.</param>
        /// <param name="cardVersion">The card version for the current request.</param>
        /// <returns>True if the current authenticated user is authorised to use the specified card identitifer and version.</returns>
        /// <remarks>An STS instance must implement a suitable authorization process customised to their authentication strategy.</remarks>
        protected abstract bool AuthoriseCardUse(string cardId, int cardVersion);

        /// <summary>
        /// Gets the user identifier from card identifier.
        /// </summary>
        /// <param name="cardID">The card ID.</param>
        /// <returns>A user identifier.</returns>
        protected abstract object GetUserIdentifierFromCardId(string cardID);
    }
}